﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using Microsoft.SqlServer.Server;

namespace KvDbApi
{
    internal static class DataExtensions
    {
        public static IDbCommand CreateKvGet(
            this IDbConnection connection, 
            ValueHandle handle,
            bool inMemory,
            out SqlParameter versionOutput,
            out SqlParameter expiryOutput)
        {
            versionOutput = CreateParamForVersionOutput();
            expiryOutput = CreateParamForExpiryOutput();

            return connection.PrepareCommand(
                inMemory ? "mem.kvGet" : "store.kvGet", 
                new[] 
                {
                    CreateParamForKeyInput(handle),
                    CreateParamForTtlInput(handle), 
                    versionOutput, 
                    expiryOutput 
                });
        }

        public static IDbCommand CreateKvMove(
             this IDbConnection connection,
             ValueHandle handle,
             out SqlParameter versionOutput,
             out SqlParameter expiryOutput)
        {
            versionOutput = CreateParamForVersionOutput();
            expiryOutput = CreateParamForExpiryOutput();

            return connection.PrepareCommand(
                "store.kvMove",
                new[] 
                {
                    CreateParamForKeyInput(handle),
                    CreateParamForTtlInput(handle), 
                    versionOutput, 
                    expiryOutput 
                });
        }

        public static IDbCommand CreateKvTouch(
            this IDbConnection connection,
            ValueHandle handle,
            out SqlParameter versionOutput,
            out SqlParameter expiryOutput)
        {
            versionOutput = CreateParamForVersionOutput();
            expiryOutput = CreateParamForExpiryOutput();

            return connection.PrepareCommand(
                "mem.kvTouch",
                new[] 
                {
                    CreateParamForKeyInput(handle),
                    CreateParamForTtlInput(handle), 
                    versionOutput, 
                    expiryOutput 
                });
        }

        public static IDbCommand CreateKvDelete(this IDbConnection connection, ValueHandle handle)
        {
            return connection.PrepareCommand(
                "mem.kvDelete",
                new[] 
                {
                    CreateParamForKeyInput(handle),
                    CreateParamForVersionInput(handle)
                });
        }

        public static IDbCommand CreateKvDestroy(this IDbConnection connection, ValueHandle handle)
        {
            return connection.PrepareCommand(
                "store.kvDestroy",
                new[] 
                {
                    CreateParamForKeyInput(handle),
                    CreateParamForVersionInput(handle)
                });
        }

        public static IDbCommand CreateKvSet(
            this IDbConnection connection,
            ValueHandle handle,
            IEnumerable<SqlDataRecord> values,
            out SqlParameter newVersionOutput,
            out SqlParameter expiryOutput)
        {
            newVersionOutput = CreateParamForNewVersionOutput();
            expiryOutput = CreateParamForExpiryOutput();

            return connection.PrepareCommand(
                "mem.kvSet",
                new[] 
                { 
                    CreateParamForKeyInput(handle), 
                    CreateParamForTtlInput(handle), 
                    CreateParamForVersionInput(handle),
                    CreateParamForHardenInput(handle.HardenToDisk), 
                    newVersionOutput, 
                    expiryOutput, 
                    CreateParamForValuesInput(values) 
                });
        }

        public static IDbCommand CreateKvAppend(
            this IDbConnection connection,
            ValueHandle handle,
            IEnumerable<SqlDataRecord> values,
            out SqlParameter newVersionOutput)
        {
            newVersionOutput = CreateParamForNewVersionOutput();

            return connection.PrepareCommand(
                "mem.kvAppend",
                new[] 
                {
                    CreateParamForKeyInput(handle),
                    CreateParamForValuesInput(values),
                    CreateParamForVersionInput(handle),
                    newVersionOutput 
                });
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage(
            "Microsoft.Security", 
            "CA2100:Review SQL queries for security vulnerabilities", 
            Justification = "Stored procedure command type not vulnerable to injection.")]
        private static IDbCommand PrepareCommand(this IDbConnection connection, string commandText, object[] parameters)
        {
            var command = connection.CreateCommand();
            command.CommandType = CommandType.StoredProcedure;
            command.CommandText = commandText;
            if (parameters != null)
            {
                var len = parameters.GetLength(0);
                for (int i = 0; i < len; i++)
                {
                    command.Parameters.Add(parameters[i]);
                }
            }

            if (connection.State != ConnectionState.Open)
            {
                connection.Open();
            }

            return command;
        }

        private static SqlParameter CreateParamForValuesInput(IEnumerable<SqlDataRecord> values)
        {
            var param = new SqlParameter("Values", SqlDbType.VarBinary);
            param.SqlDbType = SqlDbType.Structured;
            param.Value = values;
            return param;
        }

        private static SqlParameter CreateParamForHardenInput(bool harden)
        {
            var param = new SqlParameter("Harden", SqlDbType.Bit);
            param.Value = harden;
            return param;
        }

        private static SqlParameter CreateParamForKeyInput(ValueHandle handle)
        {
            var param = new SqlParameter("Key", SqlDbType.VarChar, 255);
            param.Value = handle.Key;
            return param;
        }

        private static SqlParameter CreateParamForVersionInput(ValueHandle handle)
        {
            var param = new SqlParameter("Version", SqlDbType.Int);
            param.Value = handle.DbVersion;
            return param;
        }

        private static SqlParameter CreateParamForTtlInput(ValueHandle handle)
        {
            var param = new SqlParameter("TTL", SqlDbType.Float);
            param.Value = handle.TimeToLive.TotalMilliseconds;
            return param;
        }

        private static SqlParameter CreateParamForVersionOutput()
        {
            var param = new SqlParameter("Version", SqlDbType.Int);
            param.Direction = ParameterDirection.Output;
            return param;
        }

        private static SqlParameter CreateParamForNewVersionOutput()
        {
            var param = new SqlParameter("NewVersion", SqlDbType.Int);
            param.Direction = ParameterDirection.Output;
            return param;
        }

        private static SqlParameter CreateParamForExpiryOutput()
        {
            var param = new SqlParameter("Expiry", SqlDbType.DateTime2);
            param.Direction = ParameterDirection.Output;
            return param;
        }
    }
}
